#include <nxos.h>
#include <math.h>
#include <wm/surface.h>

static int SurfaceOverlapIs(NX_Surface *s, int x, int y, int w, int h)
{
    return x < s->width && y < s->height && (x + w) > 0 && (y + h) > 0;
}

NX_Surface *NX_SurfaceAlloc(int width, int height)
{
    NX_Surface *s;

    if (width < 0 || height < 0)
        return NX_NULL;

    s = NX_MemAlloc(sizeof(NX_Surface));
    s->pixels = NX_MemAlloc(width * height * sizeof(NX_Color));
    s->width = width;
    s->height = height;
    s->stride = width * sizeof(NX_Color);

    return s;
}

void NX_SurfaceFree(NX_Surface *s)
{
    NX_MemFree(s->pixels);
    NX_MemFree(s);
    return;
}

void NX_SurfaceWrap(NX_Surface *s, NX_Color *c, int w, int h)
{
    s->width = w;
    s->height = h;
    s->stride = w * sizeof(NX_Color);
    s->pixels = c;
}

NX_Surface *NX_SurfaceAllocWrap(NX_Color *c, int width, int height)
{
    NX_Surface *s;

    if (width < 0 || height < 0)
        return NX_NULL;

    s = NX_MemAlloc(sizeof(NX_Surface));
    s->width = width;
    s->height = height;
    s->stride = width * sizeof(NX_Color);
    s->pixels = c;
    return s;
}

void NX_SurfaceClear(NX_Surface *s, NX_Color c, int x, int y, int w, int h)
{
    if (!SurfaceOverlapIs(s, x, y, w, h))
        return;

    int xs = NX_CLAMP(x, 0, s->width - 1);
    int xe = NX_CLAMP(x + w - 1, 0, s->width - 1);
    int ys = NX_CLAMP(y, 0, s->height - 1);
    int ye = NX_CLAMP(y + h - 1, 0, s->height - 1);

    for (int i = ys; i <= ye; i++)
    {
        NX_Color *pixels = &s->pixels[i * s->width];

        for (int j = xs; j <= xe; j++)
        {
            pixels[j] = c;
        }
    }
}

void NX_SurfaceSetPixel(NX_Surface *s, NX_Color color, int x, int y)
{
    if (x >= 0 && x < s->width && y >= 0 && y < s->height)
    {
        NX_Color *c = s->pixels + y * s->width + x;
        NX_ColorBlend(c, &color);
    }
}

NX_Surface *NX_SurfaceCopy(NX_Surface *s)
{
    NX_Surface *d = NX_SurfaceAlloc(s->width, s->height);

    for (int i = 0; i < d->height; i++)
    {
        NX_Color *dc = &d->pixels[i * d->width];
        NX_Color *sc = &s->pixels[i * d->width];
        for (int j = 0; j < d->width; j++)
        {
            dc[j] = sc[j];
        }
    }
    return d;
}

NX_Surface *NX_SurfaceClone(NX_Surface *s, int x, int y, int w, int h)
{
    NX_Surface *d = NX_SurfaceAlloc(w, h);
    NX_SurfaceCover(d, s, x, y);
    return d;
}

void NX_SurfaceBlit(NX_Surface *d, NX_Surface *s, int x, int y)
{
    if (!SurfaceOverlapIs(d, x, y, s->width, s->height))
        return;

    int xs = NX_CLAMP(x, 0, d->width - 1);
    int xe = NX_CLAMP(x + s->width - 1, 0, d->width - 1);
    int ys = NX_CLAMP(y, 0, d->height - 1);
    int ye = NX_CLAMP(y + s->height - 1, 0, d->height - 1);
    for (int i = ys; i <= ye; i++)
    {
        NX_Color *dl = d->pixels + i * d->width;
        NX_Color *sl = s->pixels + (i - y) * s->width - x;

        for (int j = xs; j <= xe; j++)
        {
            NX_ColorBlend(&dl[j], &sl[j]);
        }
    }
}

void NX_SurfaceBlitRegion(NX_Surface *d, NX_Surface *s, NX_Region dst, NX_Region src)
{
    if (!SurfaceOverlapIs(s, src.x, src.y, src.w, src.h))
        return;

    if (!SurfaceOverlapIs(d, dst.x, dst.y, dst.w, dst.h))
        return;

    int xs, xe, ys, ye;
    xs = NX_CLAMP(src.x, 0, s->width - 1);
    xe = NX_CLAMP(src.x + src.w - 1, 0, s->width - 1);
    ys = NX_CLAMP(src.y, 0, s->height - 1);
    ye = NX_CLAMP(src.y + src.h - 1, 0, s->height - 1);

    int dx = dst.x - src.x;
    int dy = dst.y - src.y;

    xs = NX_CLAMP(xs + dx, 0, d->width - 1);
    xe = NX_CLAMP(xe + dx, 0, d->width - 1);
    ys = NX_CLAMP(ys + dy, 0, d->height - 1);
    ye = NX_CLAMP(ye + dy, 0, d->height - 1);

    for (int i = ys; i <= ye; i++)
    {
        NX_Color *dl = d->pixels + i * d->width;
        NX_Color *sl = s->pixels + (i - dy) * s->width - dx;

        for (int j = xs; j <= xe; j++)
        {
            NX_ColorBlend(&dl[j], &sl[j]);
        }
    }
}

void NX_SurfaceBlitRegionXY(NX_Surface *d, NX_Surface *s, NX_Region dst, int x, int y)
{
    if (!SurfaceOverlapIs(d, dst.x, dst.y, dst.w, dst.h))
        return;

    NX_Region r = NX_RegionIntersect(dst, (NX_Region){x, y, s->width, s->height});

    if (NX_RegionIsEmpty(r))
        return;

    NX_SurfaceBlitRegion(d, s, r, (NX_Region){r.x - x, r.y - y, r.w, r.h});
}

void NX_SurfaceBlitWithOpacity(NX_Surface *d, NX_Surface *s, int x, int y, int a)
{
    if (!SurfaceOverlapIs(d, x, y, s->width, s->height))
        return;

    if (a == 255)
    {
        NX_SurfaceBlit(d, s, x, y);
        return;
    }

    int xs = NX_CLAMP(x, 0, d->width - 1);
    int xe = NX_CLAMP(x + s->width - 1, 0, d->width - 1);
    int ys = NX_CLAMP(y, 0, d->height - 1);
    int ye = NX_CLAMP(y + s->height - 1, 0, d->height - 1);
    for (int i = ys; i <= ye; i++)
    {
        NX_Color *dl = d->pixels + i * d->width;
        NX_Color *sl = s->pixels + (i - y) * s->width;

        for (int j = xs; j <= xe; j++)
        {
            NX_ColorBlendWithOpacity(&dl[j], &sl[j - x], a);
        }
    }
}

void NX_SurfaceMask(NX_Surface *d, NX_Surface *s, int x, int y)
{
    if (!SurfaceOverlapIs(d, x, y, s->width, s->height))
        return;

    int xs = NX_CLAMP(x, 0, d->width - 1);
    int xe = NX_CLAMP(x + s->width - 1, 0, d->width - 1);
    int ys = NX_CLAMP(y, 0, d->height - 1);
    int ye = NX_CLAMP(y + s->height - 1, 0, d->height - 1);

    NX_SurfaceMono(d, NX_ColorRGB(0x000000));
    for (int i = ys; i <= ye; i++)
    {
        NX_Color *dc = &d->pixels[i * d->width];
        NX_Color *sc = &s->pixels[(i - y) * s->width - x];

        for (int j = xs; j <= xe; j++)
        {
            NX_Color *dt = dc + j;
            NX_Color *st = sc + j;
            dt->r = st->r;
            dt->g = st->g;
            dt->b = st->b;
            dt->a = dt->a * st->a / 255.0; // TODO:
        }
    }

    for (int i = 0; i < ys; i++)
        for (int j = 0; j < d->width; j++)
            d->pixels[i * d->width + j].a = 0;
    for (int i = ye + 1; i < d->height; i++)
        for (int j = 0; j < d->width; j++)
            d->pixels[i * d->width + j].a = 0;
    for (int i = 0; i < d->height; i++)
        for (int j = 0; j < xs; j++)
            d->pixels[i * d->width + j].a = 0;
    for (int i = 0; i < d->height; i++)
        for (int j = xe + 1; j < d->width; j++)
            d->pixels[i * d->width + j].a = 0;
}

void NX_SurfaceCover(NX_Surface *d, NX_Surface *s, int x, int y)
{
    if (!SurfaceOverlapIs(d, x, y, s->width, s->height))
        return;

    int xs = NX_CLAMP(x, 0, d->width - 1);
    int xe = NX_CLAMP(x + s->width - 1, 0, d->width - 1);
    int ys = NX_CLAMP(y, 0, d->height - 1);
    int ye = NX_CLAMP(y + s->height - 1, 0, d->height - 1);
    for (int i = ys; i <= ye; i++)
    {
        NX_Color *dc = d->pixels + i * d->width;
        NX_Color *sc = s->pixels + (i - y) * s->width - x;

        for (int j = xs; j <= xe; j++)
        {
            dc[j] = sc[j];
        }
    }
}

void NX_SurfaceCompositeOut(NX_Surface *d, NX_Surface *s, int x, int y)
{
    if (!SurfaceOverlapIs(d, x, y, s->width, s->height))
        return;

    int xs = NX_CLAMP(x, 0, d->width - 1);
    int xe = NX_CLAMP(x + s->width - 1, 0, d->width - 1);
    int ys = NX_CLAMP(y, 0, d->height - 1);
    int ye = NX_CLAMP(y + s->height - 1, 0, d->height - 1);

    for (int i = ys; i <= ye; i++)
    {
        NX_Color *dc = d->pixels + i * d->width;
        NX_Color *sc = s->pixels + (i - y) * s->width - x;

        for (int j = xs; j <= xe; j++)
        {
            dc[j].a = idiv255(dc[j].a * (255 - sc[j].a));
        }
    }
}

void NX_SurfaceMono(NX_Surface *s, NX_Color color)
{
    for (int i = 0; i < s->height; i++)
    {
        NX_Color *sc = &s->pixels[i * s->width];

        for (int j = 0; j < s->width; j++)
        {
            int a = sc[j].a;
            sc[j] = color;
            sc[j].a = a;
        }
    }
}

NX_INLINE void blurinner(unsigned char *restrict p, int *restrict zr, int *restrict zg, int *restrict zb, int *restrict za, int alpha)
{
    int r, g, b;
    unsigned char a;

    b = p[0];
    g = p[1];
    r = p[2];
    a = p[3];

    *zb += (alpha * ((b << 7) - *zb)) >> 16;
    *zg += (alpha * ((g << 7) - *zg)) >> 16;
    *zr += (alpha * ((r << 7) - *zr)) >> 16;
    *za += (alpha * ((a << 7) - *za)) >> 16;

    p[0] = *zb >> 7;
    p[1] = *zg >> 7;
    p[2] = *zr >> 7;
    p[3] = *za >> 7;
}

NX_INLINE void blurrow(unsigned char *pixel, int width, int height, int channel, int line, int alpha)
{
    unsigned char *p = &(pixel[line * width * channel]);
    int zr, zg, zb, za;
    int i;

    zb = p[0] << 7;
    zg = p[1] << 7;
    zr = p[2] << 7;
    za = p[3] << 7;

    for (i = 0; i < width; i++)
        blurinner(&p[i * channel], &zr, &zg, &zb, &za, alpha);
    for (i = width - 2; i >= 0; i--)
        blurinner(&p[i * channel], &zr, &zg, &zb, &za, alpha);
}

NX_INLINE void blurcol(unsigned char *pixel, int width, int height, int channel, int x, int alpha)
{
    unsigned char *p = pixel;
    int zr, zg, zb, za;
    int i;

    p += x * channel;
    zb = p[0] << 7;
    zg = p[1] << 7;
    zr = p[2] << 7;
    za = p[3] << 7;

    for (i = width; i < (height - 1) * width; i += width)
        blurinner(&p[i * channel], &zr, &zg, &zb, &za, alpha);
    for (i = (height - 2) * width; i >= 0; i -= width)
        blurinner(&p[i * channel], &zr, &zg, &zb, &za, alpha);
}

NX_INLINE void expblur(unsigned char *pixel, int width, int height, int channel, int radius)
{
    int alpha = (int)((1 << 16) * (1.0 - expf(-2.3 / (radius + 1.0))));
    int row, col;

    for (row = 0; row < height; row++)
        blurrow(pixel, width, height, channel, row, alpha);
    for (col = 0; col < width; col++)
        blurcol(pixel, width, height, channel, col, alpha);
}

void NX_SurfaceFilterBlur(struct NX_Surface *s, int radius)
{
    int width = s->width;
    int height = s->height;
    unsigned char *pixels = (unsigned char *)s->pixels;

    if (radius > 0)
        expblur(pixels, width, height, 4, radius);
}

void NX_SurfaceFilterOpacity(struct NX_Surface *s, int a)
{
    if (a == 255)
        return;

    float fa = a / 255.0;
    for (int i = 0; i < s->height; i++)
    {
        NX_Color *sc = &s->pixels[i * s->width];

        for (int j = 0; j < s->width; j++)
        {
            sc[j].a = sc[j].a * fa;
        }
    }
}

NX_PRIVATE void NX_SurfaceBlitScaledLower(NX_Surface *dst, NX_Region *dstRect, NX_Surface *src, NX_Region *srcRect)
{
    unsigned char *srcBuf = (unsigned char *) src->pixels;
    unsigned char *dstBuf = (unsigned char *) dst->pixels;
    unsigned int *srcPixel, *dstPixel;
    
    int bitCount = sizeof(NX_Color) * 8;
    int srcLinesz = bitCount * src->width / 8;

    int desLinesz = ((dst->width * bitCount + 31) / 32) * 4;
    double rateHeight = (double)srcRect->h / dstRect->h;
    double rateWidth = (double)srcRect->w / dstRect->w;
    int i, j;

    for (i = 0; i < dstRect->h; i++) {
        /**
         * select near point
         */
        int h = (int)(rateHeight * (i + srcRect->y)); // src y
        dstPixel = (unsigned int *)&dstBuf[(i + dstRect->y) * desLinesz];
        srcPixel = (unsigned int *)&srcBuf[h * srcLinesz];
        for (j = 0; j < dstRect->w; j++) {
            int w = (int)(rateWidth * (j + srcRect->x));   // src x

            dstPixel[j + dstRect->x] = srcPixel[w];
        }
    }
}

void NX_SurfaceBlitScaled(NX_Surface *dst, NX_Region *dstRect, NX_Surface *src, NX_Region *srcRect)
{
    NX_Region srcRectNew;
    NX_Region dstRectNew;

    if (!src || !dst)
        return;

    if (!srcRect) {
        srcRectNew.x = 0;
        srcRectNew.y = 0;
        srcRectNew.w = src->width;
        srcRectNew.h = src->height;
        srcRect = &srcRectNew;
    } else {
        srcRect->w = NX_MIN(srcRect->w, src->width);
        srcRect->h = NX_MIN(srcRect->h, src->height);
        if (srcRect->x < 0)
            srcRect->x = 0;
        if (srcRect->y < 0)
            srcRect->y = 0;
        if (srcRect->x >= src->width)
            return;
        if (srcRect->y >= src->height)
            return;
    }

    if (!dstRect) {
        dstRectNew.x = 0;
        dstRectNew.y = 0;
        dstRectNew.w = dst->width;
        dstRectNew.h = dst->height + 1;
        dstRect = &dstRectNew;
    } else {
        dstRect->w = NX_MIN(dstRect->w, dst->width);
        dstRect->h = NX_MIN(dstRect->h, dst->height);
        if (dstRect->x < 0)
            dstRect->x = 0;
        if (dstRect->y < 0)
            dstRect->y = 0;
        if (dstRect->x >= dst->width)
            return;
        if (dstRect->y >= dst->height)
            return;
    }
    NX_SurfaceBlitScaledLower(dst, dstRect, src, srcRect);
}

void NX_SurfaceFullscreen(NX_Surface *dest, NX_Surface *src, int fit)
{
  if (!dest || !src)
    return;
  if (fit)
  {
    NX_Region region;

    double sacleH = (double)dest->height / (double)src->height;
    double sacleW = (double)dest->width / (double)src->width;
    if (sacleH < sacleW)
    {
      region.h = dest->height;
      region.w = (int)(src->width * sacleH);
      region.x = (dest->width / 2 - region.w / 2);
      region.y = 0;
    }
    else
    {
      region.w = dest->width;
      region.h = (int)(src->height * sacleW);
      region.y = (dest->height / 2 - region.h / 2);
      region.x = 0;
    }
    
    NX_SurfaceBlitScaled(dest, &region, src, NX_NULL);
  }
  else
  {
    NX_SurfaceBlitScaled(dest, NX_NULL, src, NX_NULL);
  }
}
